// Copyright 2017 Baidu Inc. All Rights Reserved.
// Author: Su Hao (suhao@baidu.com)
//
// Description: baidu_ca_network_socket

#include "baidu_ca_network_socket.h"
#include "baidu_ca_adapter.h"
#include "heap_monitor.h"
#include "duer_log.h"

#ifdef TARGET_UNO_91H
extern "C" {
#include "rda_trng_api.h"
}
#endif

//#define SOCKETADAPTER_DEBUG

namespace duer {

struct bca_coap_statistic_t {
    volatile size_t recv_data_size;
    volatile bca_u32_t recv_data_time;
    volatile size_t send_data_size;
    volatile bca_u32_t send_data_time;
};

static bca_coap_statistic_t s_bca_coap_statistic;

NetworkInterface* SocketAdapter::_s_interface = NULL;

SocketAdapter::SocketAdapter() : _socket(NULL) {
    memset(&_addr, 0, sizeof(_addr));
}

SocketAdapter::~SocketAdapter() {
}

void SocketAdapter::on_read() {
    DUER_LOGV("on_read: _socket = %p, _addr = %s, _port = %d, type = %d",
            _socket, _addr.host, _addr.port, _addr.type);
    _callback(&_addr);
}

int SocketAdapter::connect(const bca_addr_t* addr) {
    int rs = BCA_ERR_INTERNAL;
    mbed::Callback<void ()> callback(this, &SocketAdapter::on_read);
#ifdef TARGET_UNO_91H
    unsigned long seed = 0;
#endif
    bca_u16_t port = 0;

    if (!addr) {
        goto exit;
    }

#ifdef TARGET_UNO_91H
    rda_trng_generator((unsigned char*)&seed, sizeof(seed));
    srand(seed);
#else
    srand(time(NULL));
#endif
    port = rand() % (0x10000 - 5000) + 5000;
    DUER_LOGV("generate port: %d", port);

    _addr.type = addr->type;
    _addr.port = addr->port;
    _addr.host_size = addr->host_size;
    delete [] _addr.host;
    _addr.host = NEW(CA) char[_addr.host_size + 1];
    strncpy((char*)_addr.host, (char*)addr->host, _addr.host_size);
    *((char*)_addr.host + _addr.host_size) = 0;
    delete _socket;

    if (_addr.type == BCA_PROTO_TCP) {
        _socket = NEW(CA) TCPSocket(_s_interface);
    } else {
        _socket = NEW(CA) UDPSocket(_s_interface);
    }

    rs = _socket->bind(port);

    if (rs < 0) {
        DUER_LOGE("connect failed...rs:%d", rs);
        goto exit;
    }

    if (addr->type == BCA_PROTO_TCP) {
        rs = (reinterpret_cast<TCPSocket*>(_socket))->connect(
                (const char*)addr->host, addr->port);
    }

    _socket->attach(callback);
    _socket->set_blocking(false);
exit:
    DUER_LOGV("connect: rs = %d", rs);
    if (rs == NSAPI_ERROR_WOULD_BLOCK) {
        DUER_LOGI("connect wouldblock!!");
        //connect will not be in WOULD_BLOCK
        //if get the WOULD_BLOCK , map to internal error
        rs = BCA_ERR_TRANS_INTERNAL_ERROR;
    } else if (rs < 0) {
        DUER_LOGE("connect failed: rs = %d", rs);
        rs = BCA_ERR_TRANS_INTERNAL_ERROR;
    } else {
        // Do nothing
    }
    return rs;
}

int SocketAdapter::send(const void* data, size_t size) {
    bca_status_t rs = BCA_ERR_TRANS_INTERNAL_ERROR;

    if (_socket) {
        bca_u32_t start_send_time = us_ticker_read();
        if (_addr.type == BCA_PROTO_TCP) {
            rs = (reinterpret_cast<TCPSocket*>(_socket))->send(data, size);
        } else {
            rs = (reinterpret_cast<UDPSocket*>(_socket))->sendto(
                    (const char*)_addr.host, _addr.port, data, size);
        }
        bca_u32_t end_send_time = us_ticker_read();
#ifdef SOCKETADAPTER_DEBUG
        static int count = 0;
        DUER_LOGI("send: count = %d, rs: %d;", ++count, rs);
#endif
        s_bca_coap_statistic.send_data_time += end_send_time - start_send_time;
        if (rs > 0) {
            s_bca_coap_statistic.send_data_size += rs; // statistic the send data
        } else if (rs == NSAPI_ERROR_WOULD_BLOCK) {
            rs = BCA_ERR_TRANS_WOULD_BLOCK;
        } else if (rs == NSAPI_ERROR_NO_MEMORY) {
            // NSAPI_ERROR_NO_MEMORY, most time is send queue is full, so give it a chance to retry
            DUER_LOGI("send map NSAPI_ERROR_NO_MEMORY to BCA_ERR_TRANS_WOULD_BLOCK");
            rs = BCA_ERR_TRANS_WOULD_BLOCK;
        } else if (rs <= 0) {
            DUER_LOGNE("send: rs = %d, size:%d", rs, size);
            rs = BCA_ERR_TRANS_INTERNAL_ERROR;
        } else {
            // Do nothing
        }
    }

    return rs;
}

int SocketAdapter::recv(void* data, size_t size) {
    bca_status_t rs = BCA_ERR_TRANS_INTERNAL_ERROR;

    if (_socket) {
        bca_u32_t start_recv_time = us_ticker_read();
        if (_addr.type == BCA_PROTO_TCP) {
            rs = (reinterpret_cast<TCPSocket*>(_socket))->recv(data, size);
        } else {
            rs = (reinterpret_cast<UDPSocket*>(_socket))->recvfrom(NULL, data, size);
        }
        bca_u32_t end_recv_time = us_ticker_read();
        DUER_LOGV("recv: rs = %d, soc = %p", rs, _socket);
#ifdef SOCKETADAPTER_DEBUG
        static int count = 0;
        DUER_LOGI("recv: count = %d, rs: %d;", ++count, rs);
#endif
        s_bca_coap_statistic.recv_data_time += end_recv_time - start_recv_time;
        if (rs > 0) {
            s_bca_coap_statistic.recv_data_size += rs; // statistic the received data
        } else if (rs == NSAPI_ERROR_WOULD_BLOCK) {
            rs = BCA_ERR_TRANS_WOULD_BLOCK;
        } else if (rs <= 0) {
            DUER_LOGNW("recv: rs = %d", rs);
            rs = BCA_ERR_TRANS_INTERNAL_ERROR;
        } else {
            //do nothing
        }
    }

    return rs;
}

int SocketAdapter::close() {
#ifdef SOCKETADAPTER_DEBUG
    DUER_LOGI("socket close: %p", _socket);
#endif
    if (_socket) {
        _socket->close();
        delete _socket;
        _socket = NULL;
    }

    delete _addr.host;
    _addr.host = NULL;
    return BCA_NO_ERR;
}

void SocketAdapter::attach(Callback callback) {
    _callback = callback;
    DUER_LOGV("SocketAdapter::attach: _callback");
}

void SocketAdapter::set_network_interface(NetworkInterface* ifc) {
    _s_interface = ifc;
    DUER_LOGV("SocketAdapter::set_networks_interface: _s_interface = %p", _s_interface);
}

NetworkInterface* SocketAdapter::get_network_interface() {
    return _s_interface;
}

// after the get, the statistic will restart
void SocketAdapter::bca_coap_get_statistic(float* upload_speed, float* download_speed) {
    if (upload_speed == NULL || download_speed == NULL) {
        return;
    }

    //get the statistic values
    bca_u32_t recv_data_size = s_bca_coap_statistic.recv_data_size;
    bca_u32_t send_data_size = s_bca_coap_statistic.send_data_size;
    bca_u32_t recv_data_time = s_bca_coap_statistic.recv_data_time;
    bca_u32_t send_data_time = s_bca_coap_statistic.send_data_time;
    //restart the statistic
    s_bca_coap_statistic.recv_data_size = 0;
    s_bca_coap_statistic.recv_data_time = 0;
    s_bca_coap_statistic.send_data_size = 0;
    s_bca_coap_statistic.send_data_time = 0;


    // the unit of speed is k/s
    if (recv_data_time <= 0) {
        *download_speed = 0;
    } else {
        *download_speed = (recv_data_size / 1024.0) / (recv_data_time / 1000000.0);
    }
    DUER_LOGV("leliang: recv speed: %6.3fKB/S", *download_speed);

    if (send_data_time <= 0) {
        *upload_speed = 0;
    } else {
        *upload_speed = (send_data_size / 1024.0) / (send_data_time / 1000000.0);
    }
    DUER_LOGV("leliang: send speed: %6.3fKB/S", *upload_speed);


    return;
}

} // namespace duer

